package org.deltaset.meta.internal;

import org.deltaset.meta.annotation.DsFinder;
import org.deltaset.meta.annotation.DsTitle;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import org.deltaset.meta.internal.ReflectionUtils.Filter;
import org.deltaset.meta.annotation.DsAcceptEntity;
import org.deltaset.meta.annotation.DsDefaultFinder;
import org.deltaset.meta.annotation.DsFactoryMethod;
import org.deltaset.meta.annotation.DsFindById;
import org.deltaset.meta.annotation.DsReturnEntity;
import static org.deltaset.meta.internal.MetaActionType.*;

/**
 *
 * @author dmakariev
 */
class MetaActionTypeSetFactory {

    private final Method method;
    private final Class entityClass;
    private final Class executeClass;
    private final Set<MetaActionType> actionTypeSet;

    static Set<MetaActionType> createSet(Method method, Class entityClass, Class executeClass) {
        final MetaActionTypeSetFactory factory = new MetaActionTypeSetFactory(method, entityClass, executeClass);
        return factory.buildSet();
    }

    private MetaActionTypeSetFactory(final Method method, final Class entityClass, final Class executeClass) {
        this.method = method;
        this.entityClass = entityClass;
        this.executeClass = executeClass;
        this.actionTypeSet = EnumSet.noneOf(MetaActionType.class);
    }

    Set<MetaActionType> buildSet() {
        if (actionTypeSet.isEmpty()) {
            addIf(TITLE, isTitle);
            addIf(FACTORY_METHOD, isFactoryMethod);
            addIf(DEFAULT_FINDER, isDefaultFinder);
            addIf(FINDER, isFinder);
            addIf(ByID, isFindById);
            //addIf(EDIT, isEdit);
            addIf(ENTITY_MEMBER, isEntityMember);
            addIf(PAGING, hasPaging);
            addIf(ACCEPT_ENTITY, acceptEntity);
            addIf(ZERO_PARAMETERS, hasNoParameters);
            addIf(SINGLE_PARAMETER, hasSingleParameter);
            addIf(RETURN_COLLECTION, returnsCollection);
            addIf(RETURN_ENTITY_COLLECTION, returnsEntityCollection);
            addIf(RETURN_ENTITY, returnsEntity);
            addIf(RETURN_VOID, returnsVoid);
            addIf(RETURN_SIMPLE, returnsSimple);
            addIf(RETURN_OBJECT, returnsObject);
            addIf(HELPER, isHelper);
        }
        return actionTypeSet;
    }

    private void addIf(final MetaActionType actionType, final Filter<MetaActionTypeSetFactory> filter) {
        if (filter.isSatisfied(this)) {
            actionTypeSet.add(actionType);
        }
    }
////// definitions of internal filters;    
    private static final Filter<MetaActionTypeSetFactory> isFactoryMethod = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final boolean isFactoryMethod = method.isAnnotationPresent(DsFactoryMethod.class);
            return isFactoryMethod;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> isFindById = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final String methodName = method.getName();
            final boolean isNamedOrAnnotatedFindById = methodName.equals("findById") || method.isAnnotationPresent(DsFindById.class);
            final boolean hasSingleArgument = method.getParameterTypes().length == 1;
            //final Class methodReturnType = method.getReturnType();
            //final boolean isReturningEntityOrObject = methodReturnType.equals(entityClass) || methodReturnType.equals(Object.class);
            final boolean isFindByIdMethod = isNamedOrAnnotatedFindById && hasSingleArgument;// && isReturningEntityOrObject;
            return isFindByIdMethod;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> isEntityMember = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Class entityClass = object.entityClass;
            final Class executeClass = object.executeClass;

            final boolean isEntityMember = executeClass.equals(entityClass);

            return isEntityMember;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> hasPaging = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            return ReflectionUtils.isPagedFinder(method);
        }
    };
    private static final Filter<MetaActionTypeSetFactory> acceptEntity = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;
            final Class entityClass = object.entityClass;

            final Class[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length < 1) {
                return false;
            }
            final Class firstParameterType = parameterTypes[0];
            return entityClass.equals(firstParameterType) || method.isAnnotationPresent(DsAcceptEntity.class);
        }
    };
    private static final Filter<MetaActionTypeSetFactory> hasNoParameters = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final boolean hasNoParameters = method.getParameterTypes().length == 0;
            return hasNoParameters;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> hasSingleParameter = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final boolean hasSingleParameter = method.getParameterTypes().length == 1;
            return hasSingleParameter;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> isTitle = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;
            final Class entityClass = object.entityClass;
            final Class executeClass = object.executeClass;

            final String methodName = method.getName();
            final boolean isTitle = methodName.equals("title") || method.isAnnotationPresent(DsTitle.class) || methodName.equals("toString");
            final boolean isReturnTypeString = method.getReturnType().equals(String.class);
            final boolean hasZeroArguments = method.getParameterTypes().length == 0;
            final boolean isInEntity = executeClass.equals(entityClass);
            final boolean isTitleMethod = isTitle && isReturnTypeString && hasZeroArguments && isInEntity;
            return isTitleMethod;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> returnsCollection = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final Class returnType = method.getReturnType();
            final boolean isCollection = Collection.class.isAssignableFrom(returnType);
            return isCollection;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> returnsObject = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;
            final Class entityClass = object.entityClass;

            final Class returnType = method.getReturnType();
            final boolean isCollection = Collection.class.isAssignableFrom(returnType);
            final boolean isSimple = ReflectionUtils.isPrimitiveType(returnType);
            final boolean isEntity = entityClass.equals(returnType);

            final boolean isObject = !(isCollection || isSimple || isEntity);

            return isObject;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> returnsEntity = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;
            final Class entityClass = object.entityClass;

            final Class returnType = method.getReturnType();
            final boolean isEntityClass = entityClass.equals(returnType);
            final boolean isAnnotated = method.isAnnotationPresent(DsReturnEntity.class);
            return isEntityClass || isAnnotated;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> returnsVoid = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final Class returnType = method.getReturnType();
            final boolean isVoid = returnType.equals(Void.class) || returnType.equals(Void.TYPE);
            return isVoid;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> returnsSimple = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final Class returnType = method.getReturnType();
            final boolean isSimple = ReflectionUtils.isPrimitiveType(returnType);
            return isSimple;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> isFinder = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final String methodName = method.getName();
            final boolean isFinder = method.isAnnotationPresent(DsFinder.class) || methodName.startsWith("find");
            return isFinder;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> isHelper = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final String methodName = method.getName();
            final boolean isHelper =
                    methodName.startsWith("count")
                    || methodName.equals("title")
                    || methodName.startsWith("disable")
                    || methodName.startsWith("hide");

            return isHelper;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> returnsEntityCollection = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;
            final Class entityClass = object.entityClass;

            final Type genericReturnType = method.getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType) {
                final ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                final Type acctualType = parameterizedType.getActualTypeArguments()[0];
                if (acctualType instanceof Class) {
                    final Class parameterClass = (Class) parameterizedType.getActualTypeArguments()[0];
                    final boolean isParameterClassAnEntity = parameterClass.equals(entityClass);
                    final boolean isCollection = Collection.class.isAssignableFrom((Class) parameterizedType.getRawType());
                    return isCollection && isParameterClassAnEntity;
                }
            }
            return false;
        }
    };
    private static final Filter<MetaActionTypeSetFactory> isDefaultFinder = new Filter<MetaActionTypeSetFactory>() {

        @Override
        public boolean isSatisfied(MetaActionTypeSetFactory object) {
            final Method method = object.method;

            final boolean isDefaultFinder = method.isAnnotationPresent(DsDefaultFinder.class) || "findAll".equals(method.getName());
            return isDefaultFinder;
        }
    };
}
